import math
import numpy as np
import scipy

import coin.proto.coin_market_enums_pb2 as cme_pb2


def cnorm(x):
  return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0


def pnorm(x):
  return math.exp(-0.5 * x ** 2) / math.sqrt(2.0 * math.pi)


def calculate_black_price(*,
                          F: float,
                          r: float,
                          vol: float,
                          T: int,
                          K: float,
                          side: int,
                          curr_ts: int):
  ttm = (T - curr_ts) / 10**9 / (365 * 24 * 60 * 60)
  stdev = vol * math.sqrt(ttm)
  d1 = math.log(F / K) / stdev + stdev / (2 * vol)
  d2 = d1 - stdev

  if side == cme_pb2.OPTIONS_SIDE_CALL:
    return math.exp(-r * ttm) * (F * cnorm(d1) - cnorm(d2) * K)
  elif side == cme_pb2.OPTIONS_SIDE_PUT:
    return math.exp(-r * ttm) * (K * cnorm(-d2) - cnorm(-d1) * F)
  else:
    raise ValueError("not supported options side", side)


def calculate_bs_price(*,
                       underlying_price: float,
                       risk_free_rate: float,
                       implied_volatility: float,
                       strike_price: float,
                       maturity: int,
                       side: int,
                       current_timestamp: int):
  ttm = (maturity - current_timestamp) / 10**9 / (365 * 24 * 60 * 60)
  d1 = (np.log(underlying_price / strike_price) +
        (risk_free_rate + 0.5 * implied_volatility**2) * ttm) / (
           implied_volatility * np.sqrt(ttm))
  d2 = d1 - implied_volatility * np.sqrt(ttm)
  if side == cme_pb2.OPTIONS_SIDE_CALL:
    price = underlying_price * scipy.stats.norm.cdf(d1) - strike_price * np.exp(
        -risk_free_rate * ttm) * scipy.stats.norm.cdf(d2)
  elif side == cme_pb2.OPTIONS_SIDE_PUT:
    price = scipy.stats.norm.cdf(-d2) * strike_price * np.exp(-risk_free_rate * ttm) - \
            scipy.stats.norm.cdf(-d1) * underlying_price
  else:
    raise ValueError("not supported options side", side)
  return price
